home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995 February: Tool Chest / Dev.CD Feb 95 / Dev.CD Feb 95.toast / Sample Code / Pascal Sample 3.0B10 / Source / SampleDialog.inc1.p < prev    next >
Encoding:
Text File  |  1993-10-13  |  49.2 KB  |  1,457 lines  |  [TEXT/MPS ]

  1. (******************************************************************************
  2. *
  3. *    Apple Macintosh Developer Technical Support
  4. *
  5. *    Code for the dialog routines
  6. *
  7. *    Program:    Sample 3.0
  8. *    FILE:        SampleDialog.inc1.p - Pascal implementation
  9. *
  10. *    by:            Matt Deatherage
  11. *
  12. *    Copyright © 1988-1993 Apple Computer, Inc.
  13. *    All rights reserved.
  14. *
  15. *******************************************************************************
  16.  
  17. (*******************************************************************************
  18. * Types
  19. *
  20. * PopUpRecord is a record we use to hold the items necessary to define a pop-up
  21. * menu -- the currently chosen item number and a Handle to the actual menu
  22. *
  23. *******************************************************************************)
  24.  
  25. TYPE
  26.  
  27.     PopUpRecord = RECORD
  28.         chosenItem: INTEGER;
  29.         menuHandle: MenuHandle;
  30.         END;
  31.  
  32. (*******************************************************************************
  33. * Private global variables maintained by this unit
  34. *******************************************************************************)
  35.  
  36. VAR
  37.     
  38.     { currentCircle is a global variable that holds a circle record,
  39.       and it's this record that we modify.  When we initialize, we
  40.       copy the circle we're passed into this variable, and we copy
  41.       it back when we close the dialog if the user didn't cancel. }
  42.     
  43.     currentCircle: CircleRec;        { the circle we're modifying }
  44.     fontPopUpRecord,                { record for the font pop-up }
  45.     sizePopUpRecord: PopUpRecord;    { record for the size pop-up }
  46.  
  47.     oldCircleHeight: LONGINT;         { for estimating maximum point size }
  48.  
  49. PROCEDURE DoEvent(theEvent: EventRecord);
  50.     EXTERNAL;                        { in Sample.p; used to handle updates }
  51.  
  52. {$S Dialogs}
  53. (******************************************************************************
  54. *
  55. * private: DrawPopUpItem
  56. *
  57. * This routine takes an item number and draws the "unpopped" state of the
  58. * pop-up menu user item, including title, drop shadow and down-pointing
  59. * triangle.  This does not use the pop-up menu CDEF because we have to run
  60. * under System 6 as well.  Otherwise we'd make this a lot easier and use
  61. * the CDEF.
  62. *
  63. * The guidelines for the rectangle, down arrow and drop shadow are taken
  64. * from Human Interface Note #9, which was later folded into Inside Macintosh
  65. * Volume VI and Macintosh Human Interface Guidelines.  The down arrow is 'SICN'
  66. * resource #128, and we assume this SICN fits between the framed areas at the
  67. * right edge of the rectangle we draw.  in other words, if the rectangle is
  68. * 19 pixels tall, there will be one line of frame above the SICN, one line
  69. * of frame below the SICN and one line of drop shadow below that.  The arrow
  70. * is at the left edge of the SICN and the SICN is drawn 18 pixels to the right
  71. * of the edge of the item rectangle.  We create a small BitMap and use
  72. * CopyBits to draw the triangle.
  73. *
  74. * That note also shows a text baseline of 13 pixels below the top of the
  75. * item rectangle, so that's what we use as well for text drawing.  Before
  76. * drawing the actual string, we use MeasureText and walk through possible
  77. * string widths, looking to shorten the item string and append the "…"
  78. * character to a truncated copy of the string if it's too long to fit in the
  79. * item box.  The item box is the same width as the menu, per Macintosh
  80. * Human Interface Guidelines, even though that always means the longest
  81. * item will be truncated in the unpopped state.
  82. *
  83. * This isn't as international-friendly as it ought to be; the "…" character
  84. * should really come from a string resource so it can be changed for character
  85. * sets that don't have it as the same ASCII value.
  86. ******************************************************************************)
  87.  
  88. PROCEDURE DrawPopUpItem(theWindow: WindowPtr; itemNo: INTEGER);
  89.  
  90. VAR
  91.     ourItemType: INTEGER;                    { itemType returned by GetDItem }
  92.     ourItem: Handle;                        { item returned by GetDItem }
  93.     ourItemBox: Rect;                        { itemBox returned by GetDItem }
  94.     popUpFrameRect: Rect;                    { frame rectangle for pop-up }
  95.     iconHandle: Handle;                        { Handle to triangle SICN }
  96.     iconBitMap: BitMap;                        { BitMap for triangle SICN }
  97.     iconRect: Rect;                            { rectangle for drawing triangle }
  98.     ourPopUpRecord: PopUpRecord;            { the pop-up to draw } 
  99.     titleString: Str255;                    { item chosen currently in pop-up }
  100.     charWidths: ARRAY [0..255] OF INTEGER;    { widths of characters to use }
  101.     ellipsisWidth,                            { width of the "…" character }
  102.     titleWidth,                                { width of the chosen item string }
  103.     count,                                    { loop variable counter }
  104.     truncatedCount: INTEGER;                { count of characters such that
  105.                                               the string width is less than or
  106.                                               equal to the available width for
  107.                                               the title }
  108.  
  109. BEGIN
  110.     
  111.     { Get the item information for the requested dialog item }
  112.     
  113.     GetDItem(DialogPtr(theWindow), itemNo, ourItemType, ourItem, ourItemBox);
  114.  
  115.     { Draw the drop shadow }
  116.  
  117.     MoveTo(ourItemBox.right - 1, ourItemBox.top + 2);
  118.     lineTo(ourItemBox.right - 1, ourItemBox.bottom);
  119.     lineTo(ourItemBox.left + 2, ourItemBox.bottom);
  120.  
  121.     { frame the pop-up's rectangle }
  122.  
  123.     popUpFrameRect := ourItemBox;
  124.     popUpFrameRect.right := popUpFrameRect.right - 1;
  125.     FrameRect(popUpFrameRect);
  126.     InsetRect(popUpFrameRect, 1, 1);
  127.     EraseRect(popUpFrameRect);
  128.  
  129.     { construct the BitMap for the triangle and draw it }
  130.     
  131.     iconHandle := GetResource('SICN', rPopDownArrowID);
  132.     IF ResError = noErr THEN { don't draw the arrow if the resource call fails }
  133.         BEGIN
  134.             HLock(iconHandle);
  135.             WITH iconBitMap DO
  136.                 BEGIN
  137.                     { small icons are 16 pixels by 16 pixels -- 2 bytes wide 
  138.                       by 16 tall }
  139.                     baseAddr := iconHandle^; rowBytes := 2; 
  140.                     SetRect(bounds, 0, 0, 15, 15);
  141.                 END; { with iconBitMap do }
  142.             SetRect(iconRect, ourItemBox.right - 17, ourItemBox.top + 1,
  143.                     ourItemBox.right - 2, ourItemBox.bottom - 2);
  144.             CopyBits(iconBitMap, GrafPtr(theWindow)^.portBits,
  145.                      iconBitMap.bounds, iconRect, srcCopy, NIL);
  146.             HUnlock(iconHandle);
  147.             ReleaseResource(iconHandle);
  148.         END; { if ResError = noErr }
  149.  
  150.     { get the right pop-up record }
  151.  
  152.     IF itemNo = fontPopUp THEN
  153.         ourPopUpRecord := fontPopUpRecord
  154.     ELSE
  155.         ourPopUpRecord := sizePopUpRecord;
  156.  
  157.     { get the string for the chosen item and prepare to draw it }
  158.     
  159.     GetItem(ourPopUpRecord.menuHandle, ourPopUpRecord.chosenItem, titleString);
  160.     MoveTo(ourItemBox.left + 13, ourItemBox.top + 13);
  161.  
  162.     { Now adjust the string to be truncated if necessary to fit in the box }
  163.     
  164.     titleWidth := (ourItemBox.right - 17) - (ourItemBox.left + 13);
  165.     IF StringWidth(titleString) > titleWidth THEN
  166.         BEGIN
  167.             MeasureText(length(titleString), @titleString, @charWidths);
  168.             ellipsisWidth := CharWidth('…');
  169.             truncatedCount := length(titleString);
  170.             FOR count := 1 TO length(titleString) DO
  171.                 IF charWidths[count] + ellipsisWidth < titleWidth THEN
  172.                     truncatedCount := count;
  173.             titleString := concat(copy(titleString, 1, truncatedCount), '…');
  174.         END;
  175.  
  176.     DrawString(titleString);        { yeah, sure, NOW it's easy... }
  177.  
  178. END; { DrawPopUpItem }
  179.  
  180. {$S Dialogs}
  181. (******************************************************************************
  182. *
  183. * private: DrawCircleItem
  184. *
  185. * This routine draws the userItem with one of our traffic lights in it.
  186. * It sets up the light record to show it on and active, but not printing
  187. * for the global variable currentCircle, and then calls DrawLight to draw it.
  188. *
  189. ******************************************************************************)
  190.  
  191. PROCEDURE DrawCircleItem(theWindow: WindowPtr; itemNo: INTEGER);
  192.  
  193. VAR
  194.     myLight: LightConditions;        { defines a circle and its state }
  195.  
  196. BEGIN
  197.     WITH myLight DO
  198.         BEGIN
  199.             itsOn := TRUE;                    { lit and colored }
  200.             itsActive := TRUE;                { active so it draws }
  201.             arePrinting := FALSE;            { but we're not printing }
  202.             theCircle := @currentCircle;    { and this is the circle to draw }
  203.         END;
  204.     DrawLight(myLight);
  205. END; { DrawCircleItem }
  206.  
  207. {$S Dialogs}
  208. (******************************************************************************
  209. *
  210. * private: DrawDefaultItem
  211. *
  212. * This is a standard routine that frames the default item (assumed to be
  213. * item #1) with a round Rect frame 3 pixels wide, as described in Inside
  214. * Macintosh Volume I.
  215. *
  216. ******************************************************************************)
  217.  
  218. PROCEDURE DrawDefaultItem(theWindow: WindowPtr; itemNo: INTEGER);
  219.  
  220. VAR
  221.     ourItemType: INTEGER;            { these are returned by GetDItem }
  222.     ourItem: Handle;
  223.     ourItemBox: Rect;
  224.  
  225. BEGIN
  226.  
  227.     { Get the first item and prepare for framing }
  228.  
  229.     GetDItem(DialogPtr(theWindow), 1, ourItemType, ourItem, ourItemBox);
  230.     ForeColor(blackColor);                    { frame in black, please }
  231.     PenSize(3, 3);                            { the frame is 3 pixels wide }
  232.     InsetRect(ourItemBox, -4, -4);            { Move out by four pixels each way }
  233.     FrameRoundRect(ourItemBox, 16, 16);        { frame with radius of 16 pixels }
  234.     PenNormal;                                { restore pen to normalcy }
  235. END; { DrawDefaultItem }
  236.  
  237. {$S Dialogs}
  238. (******************************************************************************
  239. *
  240. * private: DestroyCircleDialog
  241. *
  242. * This routine is the destructor -- it tears down anything built up by
  243. * InitializeCircleDialog (also in this unit).  Right now, that's just the
  244. * two pop-up menus, which must be deleted and their resources released.
  245. *
  246. ******************************************************************************)
  247.  
  248. PROCEDURE DestroyCircleDialog;
  249.  
  250. BEGIN
  251.  
  252.     DeleteMenu(mFonts);                                      { kill the menu }
  253.     ReleaseResource(Handle(fontPopUpRecord.menuHandle));  { reclaim the space }
  254.     DeleteMenu(mSize);                                      { kill the menu }
  255.     ReleaseResource(Handle(sizePopUpRecord.menuHandle));  { reclaim the space }
  256.  
  257. END; { DestroyCircleDialog }
  258.  
  259. {$S Dialogs}
  260. (******************************************************************************
  261. *
  262. * private: FindItemInMenu
  263. *
  264. * This routine takes a MenuHandle and a string and finds a menu item with
  265. * the same string, returning the number of it (or zero if there is none).
  266. *
  267. ******************************************************************************)
  268.  
  269. FUNCTION FindItemInMenu(theMenu: MenuHandle; theItem: Str255): INTEGER;
  270.  
  271. VAR
  272.     counter: INTEGER;                { loop variable }
  273.     numItems: INTEGER;                { number of items in menu }
  274.     testString: Str255;                { holds menu items to test }
  275.  
  276. BEGIN
  277.     FindItemInMenu := 0;                { initialize this to no item found }
  278.     numItems := CountMItems(theMenu);    { only call CountMItems once, here }
  279.     FOR counter := 1 TO numItems DO
  280.         BEGIN
  281.             GetItem(theMenu, counter, testString);
  282.             IF testString = theItem THEN
  283.                 FindItemInMenu := counter;
  284.         END; { for counter do }
  285. END; { FindItemInMenu }
  286.  
  287. {$S Dialogs}
  288. (******************************************************************************
  289. *
  290. * private: SetItemEnable
  291. *
  292. * SetItemEnable makes a given dialog item enabled or disabled, depending on
  293. * the input value of enableIt.
  294. *
  295. ******************************************************************************)
  296.  
  297. PROCEDURE SetItemEnable(theDialog: DialogPtr; itemNo: INTEGER;
  298.                         enableIt: BOOLEAN);
  299.  
  300. VAR
  301.     ourItemType: INTEGER;            { these are returned from GetDItem }
  302.     ourItem: Handle;
  303.     ourItemBox: Rect;
  304.  
  305. BEGIN
  306.     GetDItem(theDialog, itemNo, ourItemType, ourItem, ourItemBox);
  307.     IF enableIt THEN
  308.         SetDItem(theDialog, itemNo, BAND(ourItemType, -1 - itemDisable),
  309.                  ourItem, ourItemBox) { enable the item }
  310.     ELSE
  311.         SetDItem(theDialog, itemNo, BOR(ourItemType, itemDisable), ourItem,
  312.                  ourItemBox);        { disable the item }
  313. END; { SetItemEnable }
  314.  
  315. {$S Dialogs}
  316. (******************************************************************************
  317. *
  318. * private: GetCheckBoxItemValue
  319. *
  320. * This routine takes a DialogPtr and an item number for a checkbox item,
  321. * and returns TRUE if the box is checked and FALSE if it's not.
  322. *
  323. * You're on your own if you pass the item number of a non-checkbox item.
  324. *
  325. ******************************************************************************)
  326.  
  327. FUNCTION GetCheckBoxItemValue(theDialog: DialogPtr; itemNo: INTEGER): BOOLEAN;
  328.  
  329. VAR
  330.     ourItemType, theValue: INTEGER;        { these are returned by GetDItem }
  331.     ourItem: Handle;
  332.     ourItemBox: Rect;
  333.  
  334. BEGIN
  335.     GetDItem(theDialog, itemNo, ourItemType, ourItem, ourItemBox);
  336.     theValue := GetCtlValue(ControlHandle(ourItem));
  337.     GetCheckBoxItemValue := (theValue <> 0);    { if the value is zero, the
  338.                                                   box is not checked }
  339. END; { GetCheckBoxItemValue }
  340.  
  341. {$S Dialogs}
  342. (******************************************************************************
  343. *
  344. * private: SetCheckBoxItemValue
  345. *
  346. * SetCheckBoxItemValue takes a BOOLEAN parameter that's TRUE if the box
  347. * should be checked.  It returns a BOOLEAN that's TRUE if the value changed
  348. * over the previous one.
  349. *
  350. * You're on your own if you pass the item number of a non-checkbox item.
  351. *
  352. ******************************************************************************)
  353.  
  354. FUNCTION SetCheckBoxItemValue(theDialog: DialogPtr; itemNo: INTEGER;
  355.                               markIt: BOOLEAN): BOOLEAN;
  356.  
  357. VAR
  358.     ourItemType,                        { returned by GetDItem }
  359.     theValue: INTEGER;                    { the existing checkbox value }
  360.     ourItem: Handle;                    { returned by GetDItem }
  361.     ourItemBox: Rect;                    { returned by GetDItem }
  362.     wasItChecked: BOOLEAN;                { the former state of the checkbox }
  363.  
  364. BEGIN
  365.     GetDItem(theDialog, itemNo, ourItemType, ourItem, ourItemBox);
  366.     theValue := GetCtlValue(ControlHandle(ourItem));
  367.  
  368.     wasItChecked := (theValue = 1);        { while the box will be checked with
  369.                                           any non-zero value, we only set
  370.                                           checkbox items to 1 to check them }
  371.     IF markIt THEN
  372.         theValue := 1
  373.     ELSE
  374.         theValue := 0;
  375.     SetCtlValue(ControlHandle(ourItem), theValue);
  376.     SetCheckBoxItemValue := (markIt <> wasItChecked);
  377. END; { SetCheckBoxItemValue }
  378.  
  379. {$S Dialogs}
  380. (******************************************************************************
  381. *
  382. * private: TwiddleCheckBoxItem
  383. *
  384. * This routine toggles the state of a checkbox item.
  385. *
  386. * You're on your own if you pass the item number of a non-checkbox item.
  387. *
  388. ******************************************************************************)
  389.  
  390. PROCEDURE TwiddleCheckBoxItem(theDialog: DialogPtr; itemNo: INTEGER);
  391.  
  392. VAR
  393.     ourItemType: INTEGER;                { these are returned by GetDItem }
  394.     ourItem: Handle;
  395.     ourItemBox: Rect;
  396.  
  397. BEGIN
  398.     GetDItem(theDialog, itemNo, ourItemType, ourItem, ourItemBox);
  399.     SetCtlValue(ControlHandle(ourItem), BitXor(GetCtlValue(
  400.                             ControlHandle(ourItem)), 1));
  401.  
  402. END; { TwiddleCheckBoxItem }
  403.  
  404. {$S Dialogs}
  405. (******************************************************************************
  406. *
  407. * private: BlinkButtonItem
  408. *
  409. * This routine highlights and unhighlights a simple button item, to simulate
  410. * it being clicked on when you press a key equivalent for it.
  411. *
  412. * You're on your own if you pass the item number of a non-button item.
  413. *
  414. ******************************************************************************)
  415.  
  416. PROCEDURE BlinkButtonItem(theDialog: DialogPtr; itemNo: INTEGER);
  417.  
  418. VAR
  419.     ourItemType: INTEGER;                { returned by GetDItem }
  420.     ourItem: Handle;                    { returned by GetDItem }
  421.     ourItemBox: Rect;                    { returned by GetDItem }
  422.     theEndingTick: LONGINT;                { the TickCount after we blink }
  423.  
  424. BEGIN
  425.     GetDItem(theDialog, itemNo, ourItemType, ourItem, ourItemBox);
  426.     HiliteControl(ControlHandle(ourItem), inButton);    { highlight }
  427.     Delay(8, theEndingTick);                      { wait eight ticks }
  428.     HiliteControl(ControlHandle(ourItem), 0);          { unhighlight }
  429.  
  430. END;
  431.  
  432. {$S Dialogs}
  433. (******************************************************************************
  434. *
  435. * private: StyleToCheckBoxItems
  436. *
  437. * StyleToCheckBoxItems takes a QuickDraw text Style (really an INTEGER) and a 
  438. * starting checkbox dialog item number.  It requires eight checkbox items, 
  439. * all numbered consecutively starting with the passed item number in the order
  440. * bold, italic, underline, outline, shadow, condensed, extended, plain.  It
  441. * sets those checkboxes based on the Style record passed to it.    It
  442. * returns TRUE if any values changed. 
  443. *
  444. * The Plain box is automatically checked if no other styles are checked, 
  445. * and cleared if any other styles are checked.  Technically, I suppose
  446. * applying both condensed and extended is "plain," but we don't check for
  447. * that.
  448. *
  449. * This routine goes a bit out of the way to deal with Pascal's strict type
  450. * checking.  stylePtr is a Ptr and not an IntegerPtr because if it's
  451. * an IntegerPtr, the compiler tries to read a two-byte value at an odd
  452. * address because it knows a Style is only one-byte long and says the address
  453. * is an odd value.  This causes crashes on 68000 machines.
  454. ******************************************************************************)
  455.  
  456.  
  457. FUNCTION StyleToCheckBoxItems(theStyle: Style; startingItem: INTEGER;
  458.                               theDialog: DialogPtr): BOOLEAN;
  459.  
  460. VAR
  461.     counter,                                 { loop variable for counting }
  462.     ourIntStyle: INTEGER;                    { Style as an INTEGER, not record }
  463.     stylePtr: Ptr;                            { Pointer to make above variable }
  464.     markIt,                                    { TRUE if a box should be checked }
  465.     plain,                                    { TRUE if this is really plain }
  466.     anyChanges: BOOLEAN;                    { TRUE if any boxes changed states }
  467.  
  468. BEGIN
  469.  
  470.     stylePtr := @theStyle;                     { we can assign this as a pointer }
  471.     ourIntStyle := stylePtr^;                { dereference it }
  472.     plain := TRUE;                             { assume this is the plain style }
  473.     anyChanges := FALSE;                    { assume we won't make changes }
  474.     FOR counter := 0 TO 6 DO
  475.         BEGIN
  476.             IF BTST(ourIntStyle, counter) THEN  { if the bit is set then }
  477.                 BEGIN
  478.                     plain := FALSE;                { it's not plain, and }
  479.                     markIt := TRUE;                { it needs to be marked }
  480.                 END
  481.             ELSE
  482.                 markIt := FALSE;                { it should be cleared }
  483.  
  484.             { set the box and record any changes in our variable }
  485.  
  486.             anyChanges := (anyChanges OR SetCheckBoxItemValue(theDialog,
  487.                            startingItem + counter, markIt));
  488.         END; { for counter do }
  489.     
  490.     { here we're done with markIt, so we use it as an "ignore" variable.
  491.       We check the "Plain" box if no other boxes are set, and we disable 
  492.       the item if we check it so people can't uncheck it. }
  493.     
  494.     markIt := SetCheckBoxItemValue(theDialog, startingItem + 7, plain);
  495.     SetItemEnable(theDialog, plainBox, NOT plain);
  496.     
  497.     StyleToCheckBoxItems := anyChanges;
  498.     
  499. END; { StyleToCheckBoxItems }
  500.  
  501. {$S Dialogs}
  502. (******************************************************************************
  503. *
  504. * private: CheckBoxItemsToStyle
  505. *
  506. * CheckBoxItemsToStyle takes a starting checkbox dialog item and a number
  507. * and returns a QuickDraw text Style which is really an INTEGER but it's a
  508. * structure as far as Pascal is concerned.  It requires eight checkbox
  509. * items, all numbered consecutively starting with the passed item number in
  510. * the order bold, italic, underline, outline, shadow, condensed, extended,
  511. * plain. It sets those bits in the Style record based on the checkbox
  512. * settings.  It does not return a BOOLEAN value, unlike
  513. * StyleToCheckBoxItems, because we can just avoid calling it unless we
  514. * twiddle a checkbox item. 
  515. *
  516. * The lastHitBox parameter tells us which checkbox was clicked on last.
  517. * We only call this when we click on a checkbox to update the style in
  518. * the circle record, and if we don't special-case checking on the plain
  519. * box, we won't clear the other boxes and bits in the Style record.
  520. ******************************************************************************)
  521.  
  522. PROCEDURE CheckBoxItemsToStyle(VAR theStyle: Style; startingItem: INTEGER;
  523.                                theDialog: DialogPtr; lastHitBox: INTEGER);
  524.  
  525. VAR
  526.     counter,                            { loop variable }
  527.     ourShortStyle: INTEGER;                { necessary to cast a record }
  528.     ourLongStyle: LONGINT;                { where we build the Style word }
  529.     stylePtr: IntegerPtr;                { necessary to cast the record }
  530.     ignore,                                { throw-away variable }
  531.     Plain: BOOLEAN;                        { TRUE if the style is Plain }
  532.  
  533. BEGIN
  534.  
  535.     ourLongStyle := 0;                     { start out with the plain style }
  536.     Plain := TRUE;
  537.  
  538.     IF lastHitBox = startingItem + 7 THEN { we just hit the plain box -- either
  539.                                            do nothing or make everything plain}
  540.         BEGIN
  541.             IF GetCheckBoxItemValue(theDialog, lastHitBox) THEN
  542.             { if Plain is checked now, set everything else FALSE }
  543.                 FOR counter := 0 TO 6 DO
  544.                     ignore := SetCheckBoxItemValue(theDialog, startingItem +
  545.                                                    counter, FALSE)
  546.         END
  547.     ELSE
  548.         BEGIN
  549.             FOR counter := 0 TO 6 DO
  550.                 IF GetCheckBoxItemValue(theDialog, counter + startingItem) THEN
  551.                     BEGIN
  552.                         BSET(ourLongStyle, counter); { set bits for checked }
  553.                         Plain := FALSE;                 { boxes and note not Plain }
  554.                     END;
  555.  
  556.     { Now, only after we know the new style, can we safely set/reset boxes based 
  557.       on the "plain" choice. We set the "Plain" box (startingItem + 7) to the 
  558.       same value we have in our plain variable. }
  559.  
  560.             ignore := SetCheckBoxItemValue(theDialog, startingItem + 7, plain);
  561.  
  562.         END;
  563.  
  564.     ourLongStyle := BSL(ourLongStyle, 8);     { shift it left by eight bits to Move
  565.                                                 it into the right byte of the
  566.                                                 INTEGER }
  567.     ourShortStyle := ourLongStyle;            { make it an INTEGER, so we can }
  568.     stylePtr := @theStyle;                    { cast it to a dereferenced }
  569.     stylePtr^ := ourShortStyle;                { IntegerPtr here }
  570.  
  571. END; { CheckBoxItemsToStyle }
  572.  
  573. {$S Dialogs}
  574. (******************************************************************************
  575. *
  576. * private: AdjustSizeMenu
  577. *
  578. * AdjustSizeMenu takes a PopUpRecord and a dialog item number.    It extracts
  579. * the number from the dialog item and marks the menu item in the pop-up with
  580. * that string.  If there is no such item, one is created at the top and a
  581. * dividing line placed underneath it.  The item number is then filled with
  582. * the chosen item.     We also take a font ID number so we can outline any
  583. * additions to the size menu if they are, in fact, "real" fonts.
  584. *
  585. * textItemNo is the item of the type-in text size item.  currentSize and
  586. * currentFont are the size and font to set things based on.  If currentSize
  587. * is zero, we use the value in the type-in text size item to set the size.
  588. *  
  589. * This function makes the menu presentable, so we never have to do anything
  590. * to it until we're ready to pop it up, and this procedure does everything
  591. * we need to fix that. 
  592. ******************************************************************************)
  593.  
  594. PROCEDURE AdjustSizeMenu(theDialog: DialogPtr; VAR sizeMenu: PopUpRecord;
  595.                          textItemNo, currentSize, currentFont: INTEGER);
  596.  
  597. VAR
  598.     ourItemType,                    { returned by GetDItem }
  599.     sizePositionInMenu,                { position of our size in the menu }
  600.     startingSizeItem,                { where to start looking for the size }
  601.     menuWalker,                        { count loop variable }
  602.     theSize,                        { the size we're looking for }
  603.     theFontNum: INTEGER;            { the font family number in use }
  604.     textItemHandle: Handle;            { Handle to the type-in size item }
  605.     ourItemBox: Rect;                { returned by GetDItem }
  606.     dividerExists: BOOLEAN;            { TRUE if there's a divider in the size
  607.                                       menu (meaning there's a custom size }
  608.     theSizeText,                    { the text in the type-in size item }
  609.     tempText: Str255;                { temporary string holding variable }
  610.     theStyle: Style;                { to set the style of the menu items }
  611.     theBigSize,                        { a LONGINT to pass to StringToNum }
  612.     thisItemSize: LONGINT;            { a LONGINT for the size of each item }
  613.  
  614. BEGIN
  615.  
  616.     { First, get the type-in text item's information }
  617.  
  618.     GetDItem(theDialog, textItemNo, ourItemType, textItemHandle, ourItemBox);
  619.     GetIText(textItemHandle, theSizeText);    { then get the text of the size }
  620.     
  621.     { If we're passed zero, get the text from the type-in size item }
  622.     
  623.     IF currentSize <> 0 THEN
  624.         BEGIN
  625.             theSize := currentSize;
  626.             NumToString(theSize, theSizeText);
  627.         END
  628.     ELSE
  629.         BEGIN
  630.             StringToNum(theSizeText, theBigSize);
  631.             theSize := theBigSize;
  632.         END;
  633.  
  634.     { We already have a custom size in the menu if the second item is '-',
  635.       which is a dividing line }
  636.  
  637.     GetItem(sizeMenu.menuHandle, 2, tempText);
  638.     dividerExists := (tempText = '-');
  639.  
  640.     { First, if there's a divider, remove it and the old custom size. }
  641.  
  642.     IF dividerExists THEN
  643.         BEGIN
  644.             DelMenuItem(sizeMenu.menuHandle, 1); { kill old custom size, bump
  645.                                                     divider up to #1 }
  646.             DelMenuItem(sizeMenu.menuHandle, 1); { kill old divider }
  647.         END;
  648.         
  649.     { Now that we have the number and the String representing it, we can find
  650.       it in the menu, hopefully }
  651.  
  652.     sizePositionInMenu := FindItemInMenu(sizeMenu.menuHandle, theSizeText);
  653.     startingSizeItem := 1; { there's no custom size right now }
  654.     
  655.     { If the item is in the menu, we're fine.  If it's not, we need to
  656.       add a dividing line and a custom size item -- unless the size
  657.       is zero (maybe an empty editText field), in which case we just 
  658.       present the standard menu. }
  659.  
  660.     IF ((sizePositionInMenu = 0) AND (theSize <> 0)) THEN
  661.         BEGIN { add it! }
  662.             InsMenuItem(sizeMenu.menuHandle, '-', 0);
  663.             InsMenuItem(sizeMenu.menuHandle, theSizeText, 0);
  664.  
  665.     { If we didn't find the size in the old menu, the size we want to
  666.       use is now item #1.  outline it as appropriate, and set the loop
  667.       counter for the rest of the menu to start at item #3. }
  668.  
  669.             IF RealFont(currentFont, theSize) THEN
  670.                 theStyle := [outline]
  671.             ELSE
  672.                 theStyle := []; { set the outline Status appropriately }
  673.             SetItemStyle(sizeMenu.menuHandle, 1, theStyle);
  674.             startingSizeItem := 3;
  675.             SetItemMark(sizeMenu.menuHandle, 1, char(checkMark));
  676.             sizeMenu.chosenItem := 1;
  677.         END;
  678.  
  679.     { now, walk the menu and set all marks and outline statuses the right way }
  680.  
  681.     FOR menuWalker := startingSizeItem TO CountMItems(sizeMenu.menuHandle) DO
  682.         BEGIN
  683.             GetItem(sizeMenu.menuHandle, menuWalker, tempText);
  684.             StringToNum(tempText, thisItemSize);
  685.             IF RealFont(currentFont, thisItemSize) THEN
  686.                 theStyle := [outline]
  687.             ELSE
  688.                 theStyle := [];
  689.             SetItemStyle(sizeMenu.menuHandle, menuWalker, theStyle);
  690.  
  691.             IF (menuWalker = sizePositionInMenu) THEN
  692.                 BEGIN
  693.                     SetItemMark(sizeMenu.menuHandle, menuWalker,
  694.                                 char(checkMark));
  695.                     sizeMenu.chosenItem := menuWalker;
  696.                 END
  697.             ELSE
  698.                 SetItemMark(sizeMenu.menuHandle, menuWalker, char(noMark));
  699.  
  700.         END; { menu walking loop }
  701.  
  702.     SetIText(textItemHandle, theSizeText);        { stuff the type-in item with
  703.                                                   the correct text value }
  704.     SelIText(theDialog, textItemNo, 0, 32767);    { and select it }
  705.  
  706. END; { AdjustSizeMenu }
  707.  
  708. {$S Dialogs}
  709. (******************************************************************************
  710. *
  711. * private: MakeDialogCurrent
  712. *
  713. * This routine sets up the dialog items based on the currentCircle
  714. * global record, as a way of updating the dialog to reflect the circle
  715. * before making it visible for the first time (or whenever it needs a
  716. * global update, though we only use it in InitializeCircleDialog).
  717. *
  718. ******************************************************************************)
  719.  
  720. PROCEDURE MakeDialogCurrent(theDialog: DialogPtr);
  721.  
  722. VAR
  723.     ourItemType: INTEGER;                { returned by GetDItem }
  724.     ourItem: Handle;                    { returned by GetDItem }
  725.     ourItemBox: Rect;                    { returned by GetDItem }
  726.     dialogChanged: BOOLEAN;                { returned by StyleToCheckBoxItems }
  727.     sizeText: Str255;                    { text of size editText item }
  728.  
  729. BEGIN
  730.  
  731.     { Set up the font menu }
  732.  
  733.     fontPopUpRecord.chosenItem := FindItemInMenu(fontPopUpRecord.menuHandle,
  734.                                                  currentCircle.circleFont);
  735.  
  736.     { Set up the style checkboxes }
  737.  
  738.     dialogChanged := StyleToCheckBoxItems(currentCircle.circleFace, boldBox,
  739.                                           theDialog);
  740.  
  741.     { Set up the size edit text. Get the size editText item, then get the 
  742.       currently selected size edit text item string and copy it into the 
  743.       type-in item. }
  744.  
  745.     GetDItem(theDialog, sizeEditText, ourItemType, ourItem, ourItemBox);
  746.     GetItem(sizePopUpRecord.menuHandle, sizePopUpRecord.chosenItem, sizeText);
  747.     SetIText(ourItem, sizeText);
  748.     SelIText(theDialog, sizeEditText, 0, 32767);
  749.  
  750.     { Set up the title text }
  751.  
  752.     GetDItem(theDialog, textEditText, ourItemType, ourItem, ourItemBox);
  753.     SetIText(ourItem, currentCircle.circleText);
  754.  
  755. END; { MakeDialogCurrent }
  756.  
  757. {$S Dialogs}
  758. (******************************************************************************
  759. *
  760. * private: SampleFilterProc
  761. *
  762. * We pass SampleFilterProc to ModalDialog for the "Modify Circles" dialog.
  763. * It filters events and items hit in two ways.
  764. *
  765. * First, we filter on events.  If it's a keyDown or autoKey event, we check
  766. * to see if it's an "accept" the dialog key, or a "cancel" key, and blink
  767. * the appropriate buttons and change the items hit.  If we're in the size
  768. * editText field (noted by looking at the editField field in the dialog
  769. * record), we reject any keys that aren't digits, command keys or editing
  770. * keys.  Per Inside Macintosh: Macintosh Toolbox Essentials, we pass
  771. * update and activate events for windows other than the dialog through to 
  772. * our main event handling code, restoring the GrafPort to the dialog when done.
  773. *
  774. * Next, we filter on items hit.  If we get hits on the fontTitle or fontPopUp,
  775. * we pop up the font menu and handle any changes made, and the same for the
  776. * size menu.  If it's in the size editText field, we check the new number
  777. * entered to make sure it's a legal value for the circle sizes we have
  778. * and alert the user if it's not.  If we get hits on the circle title
  779. * editText item, we update the circle item to reflect the new text.
  780. *
  781. * We also monitor all of this to see if any of it changes the circle in the
  782. * dialog.  If it does, we invalidate the circle so it will redraw.
  783. ******************************************************************************)
  784.  
  785. FUNCTION SampleFilterProc(theDialog: DialogPtr; VAR theEvent: EventRecord;
  786.                           VAR itemHit: INTEGER): BOOLEAN;
  787.  
  788. VAR
  789.     ourItemType,                        { returned by GetDItem }
  790.     temp,                                { used for temporary storage }
  791.     theFlags: INTEGER;                    { results of ClassifyKey }
  792.     ourItem: Handle;                    { returned by GetDItem }
  793.     popUpTitleRect,                        { rectangle of pop-up's title item }
  794.     ourItemBox: Rect;                    { returned by GetDItem }
  795.     itemChosen,                            { result of PopUpMenuSelect }
  796.     tempLong: LONGINT;                    { temporary four-byte storage }
  797.     dialogChanged: BOOLEAN;                { TRUE if anything changed }
  798.     tempPoint: Point;                    { starting Point for PopUpMenuSelect }
  799.     maxRectString,                        { string holding rectangle size } 
  800.     maxPointString,                        { string holding maximum Point size }
  801.     tempString: Str255;                    { temporary string storage }
  802.  
  803. BEGIN
  804.     SampleFilterProc := FALSE;            { by default, we didn't handle event }
  805.     dialogChanged := FALSE;                { by default, nothing changed }
  806.  
  807.     { First, filter on what kind of event we got. Classify it and act based 
  808.       on what kind of key we got, or pass update/activate events for windows
  809.       other than the dialog through to the main event handler. }
  810.     
  811.     CASE theEvent.what OF
  812.  
  813.         keyDown, autoKey:
  814.             BEGIN
  815.                 theFlags := ClassifyKey(@theEvent);
  816.                 IF (BAND(theFlags, kCancelKey) <> 0) THEN
  817.                     BEGIN
  818.                         itemHit := cancel;
  819.                         BlinkButtonItem(theDialog, cancel);
  820.                         SampleFilterProc := TRUE;
  821.                     END
  822.                 ELSE IF (BAND(theFlags, kAcceptKey) <> 0) THEN
  823.                     BEGIN
  824.                         itemHit := ok;
  825.                         BlinkButtonItem(theDialog, ok);
  826.                         SampleFilterProc := TRUE;
  827.                     END
  828.                 ELSE IF (DialogPeek(theDialog)^.editField + 1 =
  829.                         sizeEditText) THEN
  830.                     IF BAND(theFlags, kDigitKey + kCommandKey + kEditKey) =
  831.                        0 THEN
  832.                         BEGIN
  833.                             SampleFilterProc := TRUE;
  834.                             SysBeep(5); { beep to indicate there's an INVALID
  835.                                          entry }
  836.                         END;
  837.             END;
  838.  
  839.         updateEvt, activateEvt:
  840.             IF (WindowPtr(theEvent.message) <> theDialog) THEN
  841.                 BEGIN
  842.                     DoEvent(theEvent);        { call main event handler }
  843.                     SetPort(theDialog);        { and set the port back }
  844.                 END;
  845.         OTHERWISE;                            { prevents undefined case errors }
  846.     END;
  847.  
  848.     { Next, filter on what item got hit. }
  849.     
  850.     CASE itemHit OF
  851.  
  852.         fontTitle, fontPopUp:
  853.             BEGIN
  854.             
  855.                 { If we hit either the fontTitle or fontPopUp, invert the
  856.                   title rectangle, track the menu with PopUpMenuSelect and
  857.                   update the pop-up and the circle if there was a change }
  858.             
  859.                 GetDItem(theDialog, fontTitle, ourItemType, ourItem,
  860.                          popUpTitleRect);
  861.                 InvertRect(popUpTitleRect);
  862.                 
  863.                 { Pop up the menu at the upper left point of the item
  864.                   box, offset by one both down and to the left so the
  865.                   text will draw in the menu where it is in the item }
  866.                 
  867.                 GetDItem(theDialog, fontPopUp, ourItemType, ourItem,
  868.                          ourItemBox);
  869.                 tempPoint := ourItemBox.topLeft;
  870.                 tempPoint.h := tempPoint.h - 1;
  871.                 tempPoint.v := tempPoint.v + 1;
  872.                 LocalToGlobal(tempPoint);
  873.                 itemChosen := PopUpMenuSelect(fontPopUpRecord.menuHandle,
  874.                                               tempPoint.v, tempPoint.h,
  875.                                               fontPopUpRecord.chosenItem);
  876.                 temp := LoWord(itemChosen);
  877.                 IF temp <> 0 THEN
  878.                     BEGIN
  879.                         fontPopUpRecord.chosenItem := temp;
  880.                         GetItem(fontPopUpRecord.menuHandle, temp, tempString);
  881.                         ChangeCircleFont(@currentCircle, tempString);
  882.                         dialogChanged := TRUE;
  883.                         InvalRect(ourItemBox); { invalidate the font pop-up
  884.                                                  menu's item box }
  885.                     END;
  886.                 InvertRect(popUpTitleRect);
  887.             END;
  888.  
  889.         sizePopUp:
  890.             BEGIN
  891.  
  892.                 { If we hit the size pop-up, adjust the size menu and track
  893.                   changes to the menu with PopUpMenuSelect.  If the user picks
  894.                   a new size, update the type-in size editText field and the
  895.                   circle item. }
  896.  
  897.                 GetDItem(theDialog, sizePopUp, ourItemType, ourItem,
  898.                          ourItemBox);
  899.                 GetFNum(currentCircle.circleFont, temp);
  900.                 AdjustSizeMenu(theDialog, sizePopUpRecord, sizeEditText, 0,
  901.                                temp);
  902.                 tempPoint := ourItemBox.topLeft; LocalToGlobal(tempPoint);
  903.                 itemChosen := PopUpMenuSelect(sizePopUpRecord.menuHandle,
  904.                                               tempPoint.v, tempPoint.h,
  905.                                               sizePopUpRecord.chosenItem);
  906.                 temp := LoWrd(itemChosen);
  907.                 IF (temp <> 0) THEN
  908.                     BEGIN
  909.                         sizePopUpRecord.chosenItem := temp;
  910.                         GetItem(sizePopUpRecord.menuHandle, temp, tempString);
  911.                         StringToNum(tempString, tempLong);
  912.                         GetDItem(theDialog, sizeEditText, ourItemType, ourItem,
  913.                                  ourItemBox);
  914.                         SetIText(ourItem, tempString);
  915.                         SelIText(theDialog, sizeEditText, 0, 32767);
  916.                         ChangeCircleTxSize(@currentCircle, tempLong); { the size
  917.                             is still in this temporary variable }
  918.                         dialogChanged := TRUE;
  919.                     END;
  920.             END;
  921.  
  922.         sizeEditText:
  923.             BEGIN
  924.                 
  925.                 { If we got a hit in the size editText item, check the new
  926.                   number entered to see if it's in line or not.  We look at
  927.                   the oldCircleHeight global variable to see the height of 
  928.                   the original circle, and make sure the point size isn't
  929.                   taller than the circle height.  If it is, Alert the user
  930.                   that the circles aren't that tall and set to the maximum
  931.                   value.  If the size changed at all, update the circle. }
  932.                 
  933.                 GetDItem(theDialog, sizeEditText, ourItemType, ourItem,
  934.                          ourItemBox);
  935.                 GetIText(ourItem, tempString);
  936.                 StringToNum(tempString, tempLong);
  937.                 IF tempLong > oldCircleHeight THEN
  938.                     BEGIN
  939.                         tempLong := oldCircleHeight;
  940.                         NumToString(tempLong, maxPointString);
  941.                         tempLong := tempLong + 1;
  942.                         NumToString(tempLong, maxRectString);
  943.                         ParamText(maxPointString, maxRectString, '', '');
  944.                         AlertUser(rSizeTooBigAlert);
  945.                         tempLong := oldCircleHeight;
  946.                         NumToString(tempLong, tempString);
  947.                         SetIText(ourItem, tempString);
  948.                         SelIText(theDialog, sizeEditText, 32767, 32767);
  949.                     END;
  950.                 IF tempLong <> currentCircle.circleTxSize THEN
  951.                     BEGIN
  952.                         ChangeCircleTxSize(@currentCircle, tempLong);
  953.                         dialogChanged := TRUE;
  954.                     END;
  955.             END;
  956.  
  957.         textEditText:
  958.             BEGIN
  959.  
  960.                 { If we get a hit in the circle text editText item, make
  961.                   sure the text has actually changed and, if it has, update
  962.                   the circle with the new text. }
  963.  
  964.                 GetDItem(theDialog, textEditText, ourItemType, ourItem,
  965.                          ourItemBox);
  966.                 GetIText(ourItem, tempString);
  967.                 IF tempString <> currentCircle.circleText THEN
  968.                     BEGIN
  969.                         ChangeCircleText(@currentCircle, tempString);
  970.                         dialogChanged := TRUE;
  971.                     END;
  972.             END;
  973.         OTHERWISE;                            { prevents undefined case errors }
  974.     END;
  975.  
  976.     IF dialogChanged THEN
  977.         InvalidateCircle(@currentCircle);    { redraw if we changed the circle }
  978.  
  979. END; { SampleFilterProc }
  980.  
  981. {$S Dialogs}
  982. (******************************************************************************
  983. *
  984. * private: InitializeCircleDialog
  985. *
  986. * InitializeCircleDialog gets the dialog from a resource and sets all the
  987. * controls to reflect the current state of the circle, and of the system.
  988. * For example, there's no "Color..." button if Color QuickDraw isn't
  989. * available.  (No sense being cruel to people.)  It returns a pointer to
  990. * the new dialog.
  991. *
  992. ******************************************************************************)
  993.  
  994. FUNCTION InitializeCircleDialog(circle: CircleRec): DialogPtr;
  995.  
  996. VAR
  997.     ourDialog: DialogPtr;                { the dialog we create }
  998.     ourItemType,                        { for Get/SetDItem }
  999.     theFont: INTEGER;                    { our circle's font number }
  1000.     ourItem: Handle;                    { for Get/SetDItem }
  1001.     ourItemBox: Rect;                    { for Get/SetDItem }
  1002.     tempWidth: INTEGER;                    { temporary width for calculations }
  1003.  
  1004. BEGIN
  1005.  
  1006.     ourDialog := GetNewDialog(circleDialogID, NIL, pointer( - 1));
  1007.  
  1008.     { The dialog is created invisibly, so now we can set it up how we please }
  1009.  
  1010.     IF ourDialog <> NIL THEN
  1011.         BEGIN
  1012.             IF NOT gHasColorQD THEN
  1013.                 HideDItem(ourDialog, colorButton); { don't show the Color button
  1014.                                                      if we can't use it }
  1015.         
  1016.             { create the font pop-up menu with AddResMenu }
  1017.             
  1018.             fontPopUpRecord.menuHandle := GetMenu(mFonts);
  1019.             AddResMenu(fontPopUpRecord.menuHandle, 'FONT');
  1020.             InsertMenu(fontPopUpRecord.menuHandle, - 1);
  1021.             fontPopUpRecord.chosenItem := 1;
  1022.         
  1023.             { Get the size pop-up menu from our resource fork }
  1024.         
  1025.             sizePopUpRecord.menuHandle := GetMenu(mSize);
  1026.             InsertMenu(sizePopUpRecord.menuHandle, - 1);
  1027.         
  1028.             { install our user item procedures }
  1029.         
  1030.             GetDItem(ourDialog, fontPopUp, ourItemType, ourItem, ourItemBox);
  1031.             CalcMenuSize(fontPopUpRecord.menuHandle);
  1032.             tempWidth := fontPopUpRecord.menuHandle^^.menuWidth + ourItemBox.left;
  1033.             IF tempWidth < ourItemBox.right THEN
  1034.                 ourItemBox.right := tempWidth;
  1035.             SetDItem(ourDialog, fontPopUp, ourItemType, Handle(@DrawPopUpItem),
  1036.                      ourItemBox);
  1037.         
  1038.             GetDItem(ourDialog, defaultUserItem, ourItemType, ourItem,
  1039.                      ourItemBox);
  1040.             SetDItem(ourDialog, defaultUserItem, ourItemType, 
  1041.                      Handle(@DrawDefaultItem),ourItemBox);
  1042.         
  1043.             GetDItem(ourDialog, circleUserItem, ourItemType, ourItem, 
  1044.                      ourItemBox);
  1045.             SetDItem(ourDialog, circleUserItem, ourItemType, 
  1046.                      Handle(@DrawCircleItem), ourItemBox);
  1047.         
  1048.             currentCircle.circleRect := ourItemBox; { Handily retrieved from
  1049.                                                       GetDItem recently }
  1050.         
  1051.            { We start with the first text item in the dialog selected, and 
  1052.                 that's the size text in this case }
  1053.         
  1054.             GetFNum(currentCircle.circleFont, theFont);
  1055.             AdjustSizeMenu(ourDialog, sizePopUpRecord, sizeEditText,
  1056.                            currentCircle.circleTxSize, theFont);
  1057.         
  1058.             MakeDialogCurrent(ourDialog); { Make the dialog reflect 
  1059.                                             currentCircle }
  1060.         
  1061.             ShowWindow(WindowPtr(ourDialog));
  1062.         END;
  1063.         
  1064.     InitializeCircleDialog := ourDialog;
  1065.  
  1066. END; { InitializeCircleDialog }
  1067.  
  1068. {$S Dialogs}
  1069. (******************************************************************************
  1070. *
  1071. * Public: DoCircleOptions
  1072. *
  1073. * DoCircleOptions presents a nice modal dialog to change a circle's attributes
  1074. * and lets you watch while you do it.  It returns TRUE if the user accepted
  1075. * changes to the circle and FALSE otherwise.
  1076. *
  1077. * The filter procedure does a lot of the work, but this routine handles
  1078. * closing down the dialog, twiddling checkboxes, changing the circle's
  1079. * color and monitoring for changes in any of the items.
  1080. *
  1081. ******************************************************************************)
  1082.  
  1083. FUNCTION DoCircleOptions(VAR circle: CircleRec): BOOLEAN;
  1084.  
  1085. VAR
  1086.     theDialog: DialogPtr;                    { the dialog we're creating }
  1087.     itemHit: INTEGER;                        { the item the user hit }
  1088.     oldCircleRect: Rect;                    { the original circle rectangle }
  1089.     killDialog,                                { TRUE if it's time to dismiss
  1090.                                               the dialog }
  1091.     dialogChanged,                            { TRUE if something just changed
  1092.                                               in the dialog }
  1093.     dialogEverChanged: BOOLEAN;                { TRUE if anything _ever_ changed
  1094.                                               in the dialog }
  1095.  
  1096. BEGIN
  1097.     DoCircleOptions := FALSE;                { by default, no changes }
  1098.     currentCircle := circle;                { copy the circle to our global 
  1099.                                               variable }
  1100.     oldCircleRect := circle.circleRect;        { keep the old Rect }
  1101.     oldCircleHeight := circle.circleRect.bottom - circle.circleRect.top;
  1102.                                             { keep the old height in a global 
  1103.                                               variable }
  1104.     theDialog := InitializeCircleDialog(currentCircle);
  1105.                                             { setup the dialog and user items }
  1106.     SetPort(GrafPtr(theDialog));            { set to the dialog }
  1107.     killDialog := FALSE;                    { not time to dismiss it }
  1108.     dialogEverChanged := FALSE;                { hasn't changed yet }
  1109.  
  1110.     REPEAT
  1111.         dialogChanged := FALSE;                { haven't changed this time through }
  1112.         ModalDialog(@SampleFilterProc, itemHit);
  1113.  
  1114.         CASE itemHit OF
  1115.  
  1116.             ok:
  1117.                 BEGIN
  1118.                     
  1119.                     { save our changes in the original circle, kill the dialog
  1120.                       and note whether there were any changes in the return
  1121.                       value }
  1122.                     
  1123.                     circle := currentCircle;
  1124.                     circle.circleRect := oldCircleRect;
  1125.                     killDialog := TRUE;
  1126.                     DoCircleOptions := dialogEverChanged;
  1127.                 END;
  1128.  
  1129.             cancel:
  1130.                 
  1131.                 { don't save any changes, just dismiss the dialog }
  1132.                 
  1133.                 killDialog := TRUE;
  1134.  
  1135.             boldBox, italicBox, underlineBox, outlineBox, shadowBox,
  1136.             condensedBox, extendedBox, plainBox:
  1137.                 BEGIN
  1138.                     
  1139.                     { Handle changes to style checkboxes here }
  1140.                     
  1141.                     TwiddleCheckBoxItem(theDialog, itemHit);
  1142.                     CheckBoxItemsToStyle(currentCircle.circleFace, boldBox,
  1143.                                          theDialog, itemHit);
  1144.                     SetItemEnable(theDialog, plainBox,
  1145.                                   NOT GetCheckBoxItemValue(theDialog, plainBox));
  1146.                     dialogChanged := TRUE;
  1147.                 END;
  1148.  
  1149.             colorButton:
  1150.                 BEGIN
  1151.                     ChangeCircleColor(@currentCircle); dialogChanged := TRUE;
  1152.                 END;
  1153.  
  1154.             fontPopUp, fontTitle, sizePopUp, sizeEditText, textEditText:
  1155.                 dialogEverChanged := TRUE;
  1156.  
  1157.             OTHERWISE;                { prevents undefined case errors }
  1158.         END;
  1159.  
  1160.         IF dialogChanged THEN
  1161.             BEGIN
  1162.                 dialogEverChanged := TRUE;
  1163.                 InvalidateCircle(@currentCircle);
  1164.             END;
  1165.  
  1166.     UNTIL killDialog;
  1167.  
  1168.     DestroyCircleDialog;                { deallocate our storage }
  1169.     DisposeDialog(theDialog);            { and dispose of the dialog }
  1170.  
  1171. END; { DoCircleOptions }
  1172.  
  1173. {$S Dialogs}
  1174. (******************************************************************************
  1175. *
  1176. * private: PrefsFilterProc
  1177. *
  1178. * This filter procedure is passed to ModalDialog when conducting the
  1179. * preferences dialog.  It's not as complicated as the SampleFilterProc --
  1180. * all the user can do besides OK and Cancel is change three editText items.
  1181. * Here we only filter on what the event is.  The code should look familiar
  1182. * to those who've read SampleFilterProc.  We pass update and activate events
  1183. * for non-dialog windows to the main event handler, as before.
  1184. *
  1185. ******************************************************************************)
  1186.  
  1187. FUNCTION PrefsFilterProc(theDialog: DialogPtr; VAR theEvent: EventRecord;
  1188.                          VAR itemHit: INTEGER): BOOLEAN;
  1189.  
  1190. VAR
  1191.     theFlags,                            { returned by ClassifyKey }
  1192.     theField: INTEGER;                    { which field is active }
  1193.  
  1194. BEGIN
  1195.     PrefsFilterProc := FALSE;            { we don't handle the event by default }
  1196.  
  1197.     CASE theEvent.what OF
  1198.  
  1199.         keyDown, autoKey:
  1200.             BEGIN
  1201.                 theFlags := ClassifyKey(@theEvent);
  1202.                 theField := DialogPeek(theDialog)^.editField + 1;
  1203.                 IF (BAND(theFlags, kCancelKey) <> 0) THEN
  1204.                     BEGIN
  1205.                         itemHit := cancel;
  1206.                         BlinkButtonItem(theDialog, cancel);
  1207.                         PrefsFilterProc := TRUE;
  1208.                     END
  1209.                 ELSE IF (BAND(theFlags, kAcceptKey) <> 0) THEN
  1210.                     BEGIN
  1211.                         itemHit := ok;
  1212.                         BlinkButtonItem(theDialog, ok);
  1213.                         PrefsFilterProc := TRUE;
  1214.                     END
  1215.                 ELSE IF (theField = numCirclesEditText) OR (theField =
  1216.                         rectSizeEditText) OR (theField =
  1217.                         circleInsetEditText) THEN
  1218.                     IF BAND(theFlags, kDigitKey + kCommandKey + kEditKey) =
  1219.                        0 THEN
  1220.                         BEGIN
  1221.                             PrefsFilterProc := TRUE;
  1222.                             SysBeep(5); { beep to indicate there's a bad 
  1223.                                           entry }
  1224.                         END;
  1225.             END;
  1226.  
  1227.         updateEvt, activateEvt:
  1228.             IF DialogPtr(theEvent.message) <> theDialog THEN
  1229.                 DoEvent(theEvent);
  1230.         OTHERWISE;                    { prevents undefined case errors }
  1231.     END;
  1232. END; { PrefsFilterProc }
  1233.  
  1234. {$S Dialogs}
  1235. (******************************************************************************
  1236. *
  1237. * Public: DoEditPreferences
  1238. *
  1239. * DoEditPreferences conducts a dialog to change the preferences stored in
  1240. * the global preferences record.
  1241. ******************************************************************************)
  1242.  
  1243. PROCEDURE DoEditPreferences;
  1244.  
  1245. VAR
  1246.     theDialog: DialogPtr;                { the actual preferences dialog }
  1247.     ourItemType,                         { for Get/SetDItem }
  1248.     itemHit: INTEGER;                    { the item returned by ModalDialog }
  1249.     ourItem: Handle;                    { for Get/SetDItem }
  1250.     ourItemBox: Rect;                    { for Get/SetDItem }
  1251.     tempString,                            { temp. conversion of nums to strings }
  1252.     realMaxString: Str255;                { string holding maximum inset value }
  1253.     tempInset,                             { temporary inset value }
  1254.     tempInsetMax,                         { maximum inset value }
  1255.     tempLong: LONGINT;                    { temporary four-byte storage }
  1256.     killDialog,                         { TRUE if time to dismiss dialog }
  1257.     acceptChanges: BOOLEAN;                { TRUE if we should accept changes }
  1258.  
  1259. BEGIN
  1260.     theDialog := GetNewDialog(prefsID, NIL, WindowPtr(-1));
  1261.     SetPort(GrafPtr(theDialog));
  1262.     killDialog := FALSE;                { not ready to dismiss yet }
  1263.     acceptChanges := FALSE;                { don't accept changes until told to }
  1264.  
  1265.     WITH gPrefsRecord DO
  1266.         BEGIN
  1267.             
  1268.             { Set the default value of our editText fields from the global
  1269.               preferences record, and select the first item's text }
  1270.             
  1271.             tempLong := maxNumCircles;
  1272.             NumToString(tempLong, tempString);
  1273.             GetDItem(theDialog, numCirclesEditText, ourItemType, ourItem,
  1274.                      ourItemBox);
  1275.             SetIText(ourItem, tempString);
  1276.  
  1277.             tempLong := circleRectSize;
  1278.             NumToString(tempLong, tempString);
  1279.             GetDItem(theDialog, rectSizeEditText, ourItemType, ourItem,
  1280.                      ourItemBox);
  1281.             SetIText(ourItem, tempString);
  1282.  
  1283.             tempLong := circleInsetSize;
  1284.             NumToString(tempLong, tempString);
  1285.             GetDItem(theDialog, circleInsetEditText, ourItemType, ourItem,
  1286.                      ourItemBox);
  1287.             SetIText(ourItem, tempString);
  1288.  
  1289.             SelIText(theDialog, numCirclesEditText, 0, 32767);
  1290.         END;
  1291.  
  1292.     { Set the DrawDefaultItem procedure to frame our default Button }
  1293.  
  1294.     GetDItem(theDialog, prefsUserItem, ourItemType, ourItem, ourItemBox);
  1295.     SetDItem(theDialog, prefsUserItem, ourItemType, Handle(@DrawDefaultItem),
  1296.              ourItemBox);
  1297.  
  1298.     REPEAT
  1299.  
  1300.         ModalDialog(@PrefsFilterProc, itemHit);
  1301.         CASE itemHit OF
  1302.  
  1303.             ok:
  1304.                 BEGIN
  1305.                     { dismiss dialog and save changes }
  1306.                     killDialog := TRUE;
  1307.                     acceptChanges := TRUE;
  1308.                 END;
  1309.  
  1310.             cancel:
  1311.                 { just dismiss dialog }
  1312.                 killDialog := TRUE;
  1313.  
  1314.             numCirclesEditText:
  1315.                 BEGIN
  1316.                     
  1317.                     { Due to static storage limitation, we can't have more
  1318.                       than ten circles, even though we ask people how many
  1319.                       is too many.  If they try to enter more than 10,
  1320.                       we alert them that this is not possible and set the
  1321.                       maximum of ten for them. }
  1322.                     
  1323.                     GetDItem(theDialog, numCirclesEditText, ourItemType,
  1324.                              ourItem, ourItemBox);
  1325.                     GetIText(ourItem, tempString);
  1326.                     StringToNum(tempString, tempLong);
  1327.                     IF (tempLong > 10) THEN
  1328.                         BEGIN
  1329.                             AlertUser(rOnlyTenCirclesID);
  1330.                             NumToString(10, tempString);
  1331.                             SetIText(ourItem, tempString);
  1332.                             SelIText(theDialog, numCirclesEditText, 0, 32767);
  1333.                         END;
  1334.                 END;
  1335.  
  1336.             rectSizeEditText:
  1337.                 BEGIN
  1338.                     
  1339.                     { We don't allow rectangle sizes bigger than 500 pixels,
  1340.                       so Alert the users that this isn't kosher. }
  1341.                     
  1342.                     GetDItem(theDialog, rectSizeEditText, ourItemType, ourItem,
  1343.                              ourItemBox);
  1344.                     GetIText(ourItem, tempString);
  1345.                     StringToNum(tempString, tempLong);
  1346.                     IF tempLong > 500 THEN
  1347.                         BEGIN
  1348.                             AlertUser(rLessThan500Please);
  1349.                             NumToString(500, tempString);
  1350.                             SetIText(ourItem, tempString);
  1351.                             SelIText(theDialog, rectSizeEditText, 0, 32767);
  1352.                         END;
  1353.                 END;
  1354.  
  1355.             circleInsetEditText:
  1356.                 BEGIN
  1357.                     
  1358.                     { Get the size of the rectangles into tempLong }
  1359.                     
  1360.                     GetDItem(theDialog, rectSizeEditText, ourItemType, ourItem,
  1361.                              ourItemBox);
  1362.                     GetIText(ourItem, tempString);
  1363.                     StringToNum(tempString, tempLong);
  1364.  
  1365.                     { Get the inset value into tempInset }
  1366.                     
  1367.                     GetDItem(theDialog, circleInsetEditText, ourItemType,
  1368.                              ourItem, ourItemBox);
  1369.                     GetIText(ourItem, tempString);
  1370.                     StringToNum(tempString, tempInset);
  1371.  
  1372.                     { To prevent teeny tiny circles, the inset can't be bigger
  1373.                       than 40% of the rectangle size.  Since the inset is on
  1374.                       each side, that would make the circle only 20% or less
  1375.                       of the rectangle size, and that's probably small enough.
  1376.                       Calculate the real maximum value and inform the user of
  1377.                       the maximum value, resetting his choice as necessary. }
  1378.                     
  1379.                     tempInsetMax := ((tempLong DIV 2) - (tempLong DIV 10));
  1380.                     IF tempInset > tempInsetMax THEN
  1381.                         BEGIN
  1382.                             NumToString(tempInsetMax, tempString);
  1383.                             ParamText(tempString, '', '', '');
  1384.                             AlertUser(rInsetTooMuch);
  1385.                             SetIText(ourItem, tempString);
  1386.                             SelIText(theDialog, circleInsetEditText, 0, 32767);
  1387.                         END;
  1388.                 END;
  1389.  
  1390.             OTHERWISE;                { prevents undefined case errors }
  1391.         END;
  1392.  
  1393.     UNTIL killDialog;
  1394.  
  1395.     IF acceptChanges THEN
  1396.         BEGIN
  1397.             WITH gPrefsRecord DO
  1398.                 BEGIN
  1399.                     
  1400.                     { Get all the values out of the dialog and set the global
  1401.                       preferences record to reflect them }
  1402.                     
  1403.                     GetDItem(theDialog, numCirclesEditText, ourItemType,
  1404.                              ourItem, ourItemBox);
  1405.                     GetIText(ourItem, tempString);
  1406.                     StringToNum(tempString, tempLong);
  1407.                     IF ((tempLong >= 1) AND (tempLong <= 10)) THEN
  1408.                         maxNumCircles := tempLong;
  1409.  
  1410.                     GetDItem(theDialog, rectSizeEditText, ourItemType, ourItem,
  1411.                              ourItemBox);
  1412.                     GetIText(ourItem, tempString);
  1413.                     StringToNum(tempString, tempLong);
  1414.                     circleRectSize := tempLong;
  1415.  
  1416.                     GetDItem(theDialog, circleInsetEditText, ourItemType,
  1417.                              ourItem, ourItemBox);
  1418.                     GetIText(ourItem, tempString);
  1419.                     StringToNum(tempString, tempLong);
  1420.                     circleInsetSize := tempLong;
  1421.  
  1422.                     { The user could have set a legal inset value for a circle,
  1423.                       but then gone and made the rectangle size so small that the
  1424.                       inset value becomes illegal.  If this happens, alert the
  1425.                       user to the problem, notify the user that you're using
  1426.                       the allowed maximum value and go on with things. }
  1427.                     
  1428.                     tempInsetMax := ((circleRectSize DIV 2) -
  1429.                                     (circleRectSize DIV 10));
  1430.                     IF (circleInsetSize > tempInsetMax) THEN
  1431.                         BEGIN
  1432.                             NumToString(circleInsetSize, tempString);
  1433.                             NumToString(tempInsetMax, realMaxString);
  1434.                             ParamText(tempString, realMaxString, '', '');
  1435.                             AlertUser(rInsetMustBeChanged);
  1436.                             circleInsetSize := tempInsetMax;
  1437.                         END;
  1438.  
  1439.                     { write the preferences back to disk }
  1440.                     
  1441.                     PutPrefsToFile(gPrefsRecord, - 1);
  1442.  
  1443.                 END;
  1444.         END;
  1445.  
  1446.     SetPort(theDialog);
  1447.     DisposeDialog(theDialog);
  1448.  
  1449. END;
  1450.